/***
 *  Platypus: Page Layout and Typesetting Software (free at platypus.pz.org)
 *
 *  Platypus is (c) Copyright 2006-09 Pacific Data Works LLC. All Rights Reserved.
 *  Licensed under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)
 */

package org.pz.platypus.parser;

import org.pz.platypus.GDD;
//import org.pz.platypus.interfaces.ICommand;

/**
 * The token that is parsed from input lines and via TokenList is passed to the output plugin.
 *
 * @author alb
 */
public class Token
{
    /** the file number and line number of the this token. */
    private Source source;

    /** token type */
    private TokenType type;

    /** token root, used only in commands */
    private String root;

    /** what would be called the lexeme in a language parser. If type = TEXT, can be very long. */
    private String content;

    /** possible list of parameters for a command */
    private CommandParameter parameter;

    public Token( final Source source,
                  final TokenType type,
                  final String root,
                  final String content,
                  final CommandParameter parameter )
    {
        this.source = source;
        this.type = type;
        this.root = root;
        this.content = content;
        this.parameter = parameter;
    }

    /*
     * This constructor used for generation of text and macro tokens.
     */
    public Token( final Source source,
                  final TokenType type,
                  final String content )
    {
        assert( type != TokenType.COMMAND );

        this.source = source;
        this.type = type;
        this.content = content;
        this.root = null;
        this.parameter = null;
    }

    /**
     * Generates a formatted string version of the contents of a token,
     * including parameter details for a command token. Used primarily
     * for dumping token lists. See TokenList.dump(), for example.
     *
     * @param gdd the GDD
     * @return formatted string version of token contents
     */
    public String toString( final GDD gdd )
    {
        // make sure the length of the type info is 21 chars (padded w/ spaces if need be)
        String blanks21 = "                     ";
        String toktyp = type.toString();
        int paddingToAdd = blanks21.length() - toktyp.length();
        if( paddingToAdd > 0 ) {
            toktyp += blanks21.substring( 0, paddingToAdd );
        }

        // don't print out the CR/LF at the end of an input line
        if( content == null ) {
            content = " ";
        }

        // handle all commands except the [cr] EOL, which is generated by Platypus
        if( type == TokenType.COMMAND && ! root.equals( "[cr]" )) {
            content = getCommandData( gdd );
        }

        if( content.endsWith( "\n" )) {
            content = content.substring( 0, content.length() - 1 );
        }

        String tokenStr = String.format( "Line %04d: %s-> %s", source.getLineNumber(),
                toktyp, content.length() < 40 ? content : content.substring( 0, 39 ) + "..." );

        return( tokenStr );
    }

    /**
     * Convert command data into dumpable strings
     *
     * @param gdd the GDD (used for Literals lookup)
     * @return string with command data.
     */
    String getCommandData( final GDD gdd )
    {
        assert( type == TokenType.COMMAND );

        String descr = " ";

//        CommandTable ctable = gdd.getCommandTable();          // commented out just to compile. s/be restored 4/12
//        ICommand c = ctable.getCommand( root );
//        if( c != null ) {
//            descr = gdd.getLit( c.getRoot() );
//        }
//
//        if( c == null || descr.equals( " " )) {
//            descr = gdd.getLit( "COMMAND_UNRECOGNIZED" );
//        }

        if( parameter != null ) {
            descr += ": ";
            if( parameter.getString() != null ) {
                descr += parameter.getString();
            }
            else {
                descr += parameter.getAmount();
                switch( parameter.getUnit() )
                {
                    case CM:    descr += " cm";     break;
                    case INCH:  descr += " in";     break;
                    case MM:    descr += " mm";     break;
                    case POINT: descr += " pt";     break;
                    case ERROR: descr += " error";  break;
                    case NONE:  descr += " ";       break;
                    case LINE:  descr += " li";     break;
                    case PIXEL: descr += " px";     break;
                    default: break;
                }
            }
        }
        return( descr );
    }

    /**
     * Complicated equals due to optional fields and embedded objects
     * @param tok the compared-to token
     * @return yea or nay
     */
    @Override
    public boolean equals( Object tok )
    {
        if( ! ( tok instanceof Token )) {
            return( false );
        }

        Token t = (Token) tok;

        if( t.getSource().getFileNumber() != source.getFileNumber() ||
            t.getSource().getLineNumber() != source.getLineNumber() ||
            t.getType() != type ) {
                return( false );
        }

        // check for nulls in content before comparing.
        if( t.getContent() == null ) {
            return( content == null );
        }
        else if ( ! t.getContent().equals( content )) {
            return( false );
        }

        // at this point we already know the token types are the same
        if( type == TokenType.COMMAND ) {
            if( root != t.getRoot() ) {
                return( false );
            }
            else if( ! parameter.equals( t.getParameter() )) {
                return( false );
            }
        }
        return( true );
    }

    @Override
    public int hashCode()
    {
        return( (int) ( source.hashCode() ^
                      ( content == null ? 243 : content.hashCode() ) *
                      ( type == null ? 111 : type.hashCode() ) +
                      ( root == null? 27 : root.hashCode() ) -
                      ( parameter == null ? 37 : parameter.hashCode() )));
    }

    /**
     * Determines whether another Token has the same file # and line # as this
     * @param t2 the other token
     * @return yes if they're the same, false if not
     */
    public boolean sourceEquals( Token t2 )
    {
        if( t2 == null ) {
            return( false );
        }

        if( t2.getSource().getFileNumber() == source.getFileNumber() &&
            t2.getSource().getLineNumber() == source.getLineNumber() ) {
            return( true );
        }

        return( false );
    }

    //==== getters and setters ====//

    public String getContent()
    {
        return( content );
    }

    public void setContent( final String newContent )
    {
        this.content = newContent;
    }

    public Source getSource()
    {
        return( source );
    }

    public CommandParameter getParameter()
    {
        return( parameter );
    }

    public String getRoot()
    {
        return( root );
    }

    public TokenType getType()
    {
        return( type );
    }
}
